import java.awt.Color;
import java.util.*;
import java.io.*;

public class CScoutThread extends Thread{
  protected Conrad _parent;
  protected IBoard _board;
  protected Color _color, _hisColor;
  protected final int NINF = Integer.MIN_VALUE + 1;
  protected final int PINF = Integer.MAX_VALUE;
  protected final int _maxDepth = 40;

  protected long _zobristTable[][] = new long[2][64];
  protected long _zobristEnemy;
  protected Hashtable _transTable = new Hashtable(200, (float)0.5);

  private void setupZobristTable(){
    Random r = new Random();

    for (int j = 0; j < 2; j++)
      for (int i = 0; i < 64; i++)
        _zobristTable[j][i] = ((long)r.nextInt() << 32) ^ r.nextInt();

    _zobristEnemy = ((long)r.nextInt() << 32) ^ r.nextInt();
  }

  private long hashBoard(IBoard board)
  {
    Color tmp, us = board.getCurrentPlayerColor();
    long res;
    if (us == _color) // if the current player is us
      res = 0;
    else
      res = _zobristEnemy;

    for (int row = 0; row < 8; row ++){
      for (int col = 0; col < 8; col ++){
        tmp = board.playerAt(row, col);
        if (tmp == _color)
          res ^= _zobristTable[0][(row<<3)|col];
        else if (tmp != null)
          res ^= _zobristTable[1][(row<<3)|col];
      }
    }

    return res;
  }

  protected int evaluate(IBoard board, Color us){
    final double[] cMaterial = new double[]{
      5000., -900., 200., 100., 100., 200., -900., 5000.,
      -900.,-1000., 500., 100., 100., 500.,-1000., -900.,
       200.,  500., 100., 100., 100., 100.,  500.,  200.,
       100.,  100., 100., 100., 100., 100.,  100.,  100.,
       100.,  100., 100., 100., 100., 100.,  100.,  100.,
       200.,  500., 100., 100., 100., 100.,  500.,  200.,
      -900.,-1000., 500., 100., 100., 500.,-1000., -900.,
      5000., -900., 200., 100., 100., 200., -900., 5000.
    };
    double score = 0;
    int pieces = 0, piecesUs = 0, piecesThem = 0;
    boolean mustPass = true;
    double material = 0., eMaterial = 0., mobility = 0., eMobility = 0.;

    Color tmp, enemy = (us == _color)? _hisColor : _color;

    // Loop through each square, counting our tiles as well as
    // mobility. Should be fairly self-explanatory
    for (int r = 0; r < 8; r++){
      for (int c = 0; c < 8; c++){
        tmp = board.playerAt(r, c);
        if (tmp == us){
          material  += cMaterial[(r<<3) | c];
          pieces++; piecesUs++;
        }
        else if(tmp == enemy){
          eMaterial += cMaterial[(r<<3) | c];
          pieces++; piecesThem++;
        }

        // else there's no one on this square
        else{
          if (board.isValidMove(us, r, c)){
            mobility  += cMaterial[(r<<3) | c];
            mustPass = false;
          }
          else if (board.isValidMove(enemy, r, c)){
            eMobility += cMaterial[(r<<3) | c];
          }
        }
      }
    }

    // Score based on part-of-game
    // I don't know where the numbers come from; might try mutating
    // them in evolutionfights later
    if (pieces < 30){
      // Beginning game, material is important, while mobility less so.
      // But we really don't want to pass this early on.
      score += (material - eMaterial)*3;
      score += (mobility - eMobility)*0.5;
      score -= mustPass? 2000000. : 0;
    }
    else if (pieces < 42){
      // Midgame, up the importance of mobility some, decrease that of
      // material... passing is semi-ok now
      score += (material - eMaterial)*1.8;
      score += (mobility - eMobility)*0.9;
      score -= mustPass? 1000000. : 0;
    }
    else if (pieces < 48){
      if (material < eMaterial) score -= 10000;
      score += (mobility - eMobility)*3;
      score -= mustPass? 1100000. : 0;
    }
    else{
      // End game, last 18 or fewer moves. Mobility counts for a lot here.
      // Check to see if this node constitutes either our victory or
      // humiliation. Compensate score accordingly.
      if (mustPass && board.isGameOver()){
        if (board.playerScore(us) > board.playerScore(enemy)){
          mustPass = false;
          score += PINF/10;
        }
        else score -= PINF/10;
      }

      if (material < eMaterial) score -= 10000;
      score += (mobility - eMobility)*5;
      score -= mustPass? 1200000. : 0;
    }

    return (int)score;
  }

  public CScoutThread(Conrad parent, IBoard board, Color color,
      Color hisColor){
    _parent   = parent;
    _color    = color;
    _hisColor = hisColor;
    _board    = board;

    setupZobristTable();

    try{
      BufferedWriter out = new BufferedWriter(new FileWriter("runlog",
            true));
      out.write("--nt\n");
      out.close();
    }
    catch (IOException ioe){
    }
  }

  public void run(){
    // iteratively deeply search each set of valid moves so we can
    // efficiently negascout
    ArrayList<Integer> moves = validMoves(_board, _color);
    int idx, depth;
    Integer bM;

    for (depth = 1; depth < _maxDepth; depth ++){
      try{
        BufferedWriter out = new BufferedWriter(new FileWriter("runlog",
              true));
        out.write("++ ");
        out.write(((Integer)depth).toString());
        out.write("\n");
        out.close();
      }
      catch (IOException ioe){
      }

      bM = topNegaScout(depth, moves);
      _parent.bestMove = bM;

      try{
        BufferedWriter out = new BufferedWriter(new FileWriter("runlog",
              true));
        out.write("       -> r, c => [");
        out.write(((Integer)((bM>>3) + 1)).toString());
        out.write(", ");
        out.write(((Integer)((bM&7) + 1)).toString());
        out.write("]\n");
        out.close();
      }
      catch (IOException ioe){
      }

      // re-order moves best first
      if (moves.size() > 0 && (idx = moves.indexOf(bM)) != 0){
        moves.set(idx, moves.get(0));
        moves.set(0,   bM);
      }
    }
  }

  protected int negaScout(IBoard node, int depth, int a, int B){
    int b, v;
    IBoard child;
    boolean first = true;

    if (node.isGameOver() || depth == 0)
      return evaluate(node, node.getCurrentPlayerColor());

    ArrayList<Integer> playerMoves = validMoves(node,
        node.getCurrentPlayerColor());

    if (playerMoves.size() == 0)
      return evaluate(node, node.getCurrentPlayerColor());

    b = B;
    for (Integer move : playerMoves){
      child = node.hypotheticallyPlacePiece(node.getCurrentPlayerColor(),
          move >> 3, move & 7);

      v = -negaScout(child, depth - 1, -b, -a);
      if (a < v && v < B && !first)
        v = -negaScout(child, depth - 1, -B, -v);
      a = (a > v)? a : v;
      if (a >= B) return a;
      b = a + 1;
    }
    return a;
  }

  protected Integer topNegaScout(int depth, ArrayList<Integer> moves){
    int a = NINF, B = PINF, v, b = PINF;
    boolean firstChild = true;
    IBoard child;
    Integer bestMove = 64;
    int bestScore = NINF;

    for (Integer move : moves){
      child = _board.hypotheticallyPlacePiece(_color, move >> 3, move & 7);

      v = -negaScout(child, depth - 1, -b, -a);
      if (a < v && v < B && !firstChild)
        v = -negaScout(child, depth - 1, -B, -v);
      a = (a > v)? a : v;

      if (a > bestScore){
        bestScore = a;
        bestMove  = move;
      }

      b = a + 1;
      firstChild = false;
    }

    return bestMove;
  }

  protected ArrayList<Integer> validMoves(IBoard board, Color playerColor){
    int r, c, count;
    ArrayList<Integer> moves = new ArrayList<Integer>();

    for (r = 0; r < 8; r++)
      for (c = 0; c < 8; c++)
        if (board.isValidMove(playerColor, r, c))
          moves.add((r << 3) | c);

    return moves;
  }
}
